In the early days of programming, a software developer confronted with the task of writing a program to perform a task would create a monolithic body of instructions for performing that task. Because of the difficulties associated with communicating with a processor using its native machine language instructions, these functions and procedures were often expressed in a more human-understandable language, often referred to as a “programming language.” This expression of instructions, referred to as “source code,” was then provided to a translator, for example an interpreter, a compiler, or an assembler, which then re-expressed it in a form that could be understood by the machine. The source code was typically a single, indivisible entity stored as a single file. This resulted in code that was difficult to maintain and debug.
As programming languages evolved, it became possible to use certain programming constructs, for example functions and procedures, to perform certain elementary tasks. These programming constructs could be self-contained entities that performed well-defined tasks on particular arguments. Their scope could be controlled to perform those tasks without interfering with the operation of other functions and procedures. Programming constructs that performed related tasks could then be stored as separate files and maintained separately from programming constructs that performed different tasks. This enabled source code to be written as independent modules by different developers. The resulting collection of modules could then be assembled into the desired software system. A software system built out of self-contained modules in this way was far easier to maintain and debug.
Because all source code is ultimately translated into machine language, the particular language used to express the source code is irrelevant to the processor. So long as the processor receives its instructions in its native machine language, it will perform correctly. This fact, combined with the idea of modular programming, suggests that the choice of programming language can be varied depending on the nature of the task to be performed. For example, a simple but frequently executed loop can be more easily optimized for performance when written in assembly language. Instructions for performing complex tasks, on the other hand, are often more easily expressed in a higher-level language.
As a software system evolves, new modules are added, obsolete modules are deleted, and existing programming constructs are re-shuffled between modules. In a large software system, these tasks are often performed by many different programmers over an extended period. This eventually results in byzantine links between modules, many of which might be obsolete or redundant. Source code that evolves in such a haphazard manner is prone to bugginess, difficult to maintain, and virtually impossible to optimize for performance.
At certain times in the evolution of software systems, usually after a major release, an urge to rectify the mistakes of their predecessors arises among the developers. In most cases, by the time this occurs, the system has become so difficult to maintain that it is virtually impossible to anticipate the full consequences of even small changes to the source code. Because of the dangers that accompany major re-organization of source code, there exists an unfortunate, but understandable, tendency for software systems to become fossilized over time.